import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Map;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

/**
 * Simple example of a robot communication interface to the (real or virtual)
 * robot over a network connection. It uses Java -> JSON -> HttpRequest ->
 * Network -> DssHost32 -> Lokarria(Robulab) -> MRDS4
 * 
 * Most methods can throw an exception - in this example, they are all thrown
 * out to the operating system. A real implementation should take care of the
 * different exception types.
 * 
 * This class needs the following Jackson JSON .jar files:
 * jackson-core-2.0.5.jar jackson-databind-2.0.5.jar
 * jackson-annotations-2.0.5.jar
 * 
 * Download them from http://wiki.fasterxml.com/JacksonDownload under Downloads,
 * 2.x.
 * 
 * @author Thomas Johansson, dept. of Computing Science, Ume� University, Ume�,
 *         Sweden Mail: thomasj@cs.umu.se
 */
public class RobotCommunication {
    private String host;
    private Gson gson = new Gson();

    /**
     * Create a new communications object. Normally the host is 'localhost' and
     * the port is 500000.
     * 
     * @param host
     * @param port
     */
    public RobotCommunication(String host) {
	this.host = host;

	// This is used by the JSON library to convert to/from JSON
    }

    /**
     * Send a request to the robot.
     * 
     * @param r
     *            request to send
     * @return response code from the connection (the web server)
     * @throws Exception
     */
    public synchronized int putRequest(final Request r) {
	try {
	    final URL url = new URL(host + r.getPath());

	    final HttpURLConnection connection = (HttpURLConnection) url
		    .openConnection();

	    connection.setDoOutput(true);

	    connection.setRequestMethod("POST");
	    connection.setRequestProperty("Content-Type", "application/json");
	    connection.setUseCaches(false);

	    final OutputStreamWriter out = new OutputStreamWriter(
		    connection.getOutputStream());

	    // construct a JSON string
	    final String json = gson.toJson(r.getData());

	    // write it to the web server
	    out.write(json);
	    out.close();

	    // wait for response code
	    final int rc = connection.getResponseCode();
	    return rc;
	} catch (Exception e) {
	    e.printStackTrace();
	}
	return 0;
    }

    /**
     * Get a response from the robot
     * 
     * @param r
     *            response to fill in
     * @return response same as parameter
     * @throws Exception
     */
    public synchronized Response getResponse(final Response r) {
	try {
	    final URL url = new URL(host + r.getPath());
	    // System.out.println(url);

	    // open a connection to the web server and then get the resulting
	    // data
	    final URLConnection connection = url.openConnection();
	    final BufferedReader in = new BufferedReader(new InputStreamReader(
		    connection.getInputStream()));

	    final Type sObjMap = new TypeToken<Map<String, Object>>() {
	    }.getType();
	    final Map<String, Object> data = gson.fromJson(in, sObjMap);

	    // map it to a Java Map
	    // final Map<String, Object> data = gson.to mapper.readValue(in,
	    // Map.class);
	    r.setData(data);

	    in.close();

	    return r;
	} catch (Exception e) {
	    e.printStackTrace();
	}
	return r;
    }

    /**
     * Get corresponding angles to each laser beam
     * 
     * @param lpr
     * @return laser angles in radians
     */
    public double[] getLaserAngles(LaserPropertiesResponse lpr) {
	int beamCount = (int) ((lpr.getEndAngle() - lpr.getStartAngle()) / lpr
	        .getAngleIncrement()) + 1;
	double[] angles = new double[beamCount];
	double a = lpr.getStartAngle();
	for (int i = 0; i < beamCount; i++) {
	    angles[i] = a;
	    // We get roundoff errors if we use AngleIncrement. Use 1 degree in
	    // radians instead
	    a += 1 * Math.PI / 180;// lpr.getAngleIncrement();
	}
	return angles;
    }
}
